package org.sean.framework.util;

import lombok.extern.slf4j.Slf4j;
import org.springframework.aop.framework.AdvisedSupport;
import org.springframework.aop.framework.AopProxy;
import org.springframework.aop.support.AopUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.util.ObjectUtils;

import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Objects;

/**
 * Object 工具类
 *
 * @author xielei
 */
@Slf4j
public final class ObjectUtil {
    private ObjectUtil() {
    }

    /**
     * 判断object是否为基本类型
     *
     * @param object object
     * @return 是否为基础类型
     */
    public static boolean isPrimitiveType(Object object) {
        return isPrimitiveClass(object.getClass());
    }

    /**
     * 判断object是否为基本类型
     *
     * @param cls class
     * @return 是否基础类型
     */
    public static boolean isPrimitiveClass(Class<?> cls) {
        return cls.isPrimitive()
                || cls.equals(Integer.class)
                || cls.equals(Byte.class)
                || cls.equals(Long.class)
                || cls.equals(Double.class)
                || cls.equals(Float.class)
                || cls.equals(Character.class)
                || cls.equals(Short.class)
                || cls.equals(Boolean.class);
    }

    /**
     * 判断object是否为基本类型
     *
     * @param object object
     * @return 是否为基础类型
     */
    public static boolean isPrimitiveTypeArray(Object object) {
        Class<?> cls = object.getClass();
        return cls.isArray() && (
                cls.equals(Integer[].class)
                        || cls.equals(Byte[].class)
                        || cls.equals(Long[].class)
                        || cls.equals(Double[].class)
                        || cls.equals(Float[].class)
                        || cls.equals(Character[].class)
                        || cls.equals(Short[].class)
                        || cls.equals(Boolean[].class));
    }

    /**
     * 转换数组
     *
     * @param obj obj
     * @return 数组对象
     */
    public static Object[] getArray(Object obj) {
        if (isArray(obj)) {
            int length = Array.getLength(obj);
            Object[] array = new Object[length];
            for (int i = 0; i < array.length; i++) {
                array[i] = Array.get(obj, i);
            }
            return array;
        }
        return null;
    }

    /**
     * 是否为数组
     *
     * @param obj obj
     * @return 是否为数组
     */
    public static boolean isArray(Object obj) {
        return ObjectUtils.isArray(obj);
    }

    /**
     * 空对象
     *
     * @param obj obj
     * @return 是否为空
     */
    public static boolean isEmpty(Object obj) {
        return ObjectUtils.isEmpty(obj);
    }

    /**
     * 对象是否不为空
     *
     * @param obj obj
     * @return 不为空
     */
    public static boolean isNotEmpty(Object obj) {
        return !isEmpty(obj);
    }

    /**
     * 是否为true
     *
     * @param value value
     * @return is True
     */
    public static boolean isTrue(Object value) {
        if (value instanceof Boolean) {
            return (boolean) value;
        } else {
            return false;
        }
    }

    /**
     * 检查是否为空
     *
     * @param obj obj
     * @return 是否为Null
     */
    public static boolean isNull(Object obj) {
        return Objects.isNull(obj);
    }

    /**
     * 检查是否为空
     *
     * @param obj obj
     * @return 是否为Null
     */
    public static boolean isNotNull(Object obj) {
        return !Objects.isNull(obj);
    }

    // ToString
    //-----------------------------------------------------------------------

    /**
     * <p>Gets the <code>toString</code> of an <code>Object</code> returning
     * an empty string ("") if <code>null</code> input.</p>
     *
     * <pre>
     * ObjectUtils.toString(null)         = ""
     * ObjectUtils.toString("")           = ""
     * ObjectUtils.toString("bat")        = "bat"
     * ObjectUtils.toString(Boolean.TRUE) = "true"
     * </pre>
     *
     * @param obj the Object to <code>toString</code>, may be null
     * @return the passed in Object's toString, or nullStr if <code>null</code> input
     * @see String#valueOf(Object)
     * @since 2.0
     */
    public static String toString(Object obj) {
        return obj == null ? "" : obj.toString();
    }

    /**
     * <p>Gets the <code>toString</code> of an <code>Object</code> returning
     * a specified text if <code>null</code> input.</p>
     *
     * <pre>
     * ObjectUtils.toString(null, null)           = null
     * ObjectUtils.toString(null, "null")         = "null"
     * ObjectUtils.toString("", "null")           = ""
     * ObjectUtils.toString("bat", "null")        = "bat"
     * ObjectUtils.toString(Boolean.TRUE, "null") = "true"
     * </pre>
     *
     * @param obj     the Object to <code>toString</code>, may be null
     * @param nullStr the String to return if <code>null</code> input, may be null
     * @return the passed in Object's toString, or nullStr if <code>null</code> input
     * @see String#valueOf(Object)
     * @since 2.0
     */
    public static String toString(Object obj, String nullStr) {
        return obj == null ? nullStr : obj.toString();
    }

    /**
     * 转换对象
     *
     * @param source 原始对象
     * @param target 目标对象
     * @param <T>    T
     * @return 转换后对象
     */
    public static <T> T convert(Object source, Class<T> target) {
        if (source == null) {
            return null;
        }
        T targetObject = null;
        try {
            targetObject = target.getDeclaredConstructor().newInstance();
            BeanUtils.copyProperties(source, targetObject);
        } catch (Exception e) {
            log.error("convert error ", e);
        }

        return targetObject;
    }

    /**
     * 转换类型
     *
     * @param sourceList 原始列表
     * @param target     目标类型
     * @param <T>        T
     * @return 目标类型数据列表
     */
    public static <T> List<T> convert(Collection<?> sourceList, Class<T> target) {
        if (sourceList == null) {
            return null;
        }

        List<T> targetList = new ArrayList<>(sourceList.size());
        try {
            for (Object source : sourceList) {
                T targetObject = target.getDeclaredConstructor().newInstance();
                BeanUtils.copyProperties(source, targetObject);
                targetList.add(targetObject);
            }
        } catch (Exception e) {
            log.error("convert error ", e);
        }

        return targetList;
    }

    /**
     * 获取 目标对象
     *
     * @param <T>   T
     * @param proxy 代理对象
     * @return 原始对象
     * @throws Exception 异常
     */
    @SuppressWarnings("unchecked")
    public static <T> T getTarget(Object proxy) throws Exception {
        return (T) getTargetObject(proxy);
    }

    /**
     * 获取 目标对象
     *
     * @param proxy 代理对象
     * @return 原始对象
     * @throws Exception 异常
     */
    public static Object getTargetObject(Object proxy) throws Exception {

        if (!AopUtils.isAopProxy(proxy)) {
            return proxy;//不是代理对象
        }

        if (AopUtils.isJdkDynamicProxy(proxy)) {
            return getJdkDynamicProxyTargetObject(proxy);
        } else { //cglib
            return getCglibProxyTargetObject(proxy);
        }

    }


    private static Object getCglibProxyTargetObject(Object proxy) throws Exception {
        Field h = proxy.getClass().getDeclaredField("CGLIB$CALLBACK_0");
        h.setAccessible(true);
        Object dynamicAdvisedInterceptor = h.get(proxy);

        Field advised = dynamicAdvisedInterceptor.getClass().getDeclaredField("advised");
        advised.setAccessible(true);

        return ((AdvisedSupport) advised.get(dynamicAdvisedInterceptor)).getTargetSource().getTarget();
    }


    private static Object getJdkDynamicProxyTargetObject(Object proxy) throws Exception {
        Field h = proxy.getClass().getSuperclass().getDeclaredField("h");
        h.setAccessible(true);
        AopProxy aopProxy = (AopProxy) h.get(proxy);

        Field advised = aopProxy.getClass().getDeclaredField("advised");
        advised.setAccessible(true);

        return ((AdvisedSupport) advised.get(aopProxy)).getTargetSource().getTarget();
    }
}
